/* Project: Side Scrolling Shooter
 * The main game class. Handles projectile firing and most collisions.
 * Last Update: 4/12/11,11:16PM
 */

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace TheFifthSpace
{
    // 3 Game states
    public enum GameState
    {
        StartScreen,
        RunningGame,
        EndScreen
    }

    /******************************************** Game1 Class *******************************************/

    public class MainGame : Microsoft.Xna.Framework.Game
    {
        // Variables for the Player:
        public static Player thePlayer;
        KeyboardState k_old, k_new;
        Keys missileButton, bombButton; // Buttons to launch missiles/bombs
        public EffectExplosion deathExplosion;
        Keys pauseButton;
        //public bool missilePressed, bombPressed, pausePressed, escPressed;

        // Global variables for the game:
        public static GameState currentState;
        private float dt, passedTime, enemyCountdown; // Changes in time
        public static int borderRightX; // Maximum X
        public int borderLeftX; // Minimum X
        public static int borderY; // Maximum Y
        public static int currentScore { get; set; } // Current score
        public int finalScore;
        public static SpriteFont Font1;
        // Locations for drawing text:
        private Vector2 scorePos, healthBarPos, healthTextPos, finalScorePos;
        private Vector2 pausePos;
        private float bombCountdown, missileCountdown; // Prevent player from firing repeatedly
        public bool paused { get; set; }
        private Vector2 start;
        private Random r;
        private int maxEnemies;

        /* Construct enemies, bullets, missiles, bombs, and
         * explosions by adding new ones to these lists:
         * (Do not add directly to Game.Components!)
         */
        public static List<Enemy> Enemies = new List<Enemy>();
        public static List<EnemyBullet> EnemyBullets = new List<EnemyBullet>();
        public static List<PlayerMissile> PlayerMissiles = new List<PlayerMissile>();
        public static List<PlayerBomb> PlayerBombs = new List<PlayerBomb>();
        public static List<BombExplosion> BombExplosions = new List<BombExplosion>();
        public static List<PowerupHealth> Powerups1 = new List<PowerupHealth>();

        // Visuals:
        GraphicsDeviceManager graphics;
        Rectangle playerRectangle;
        public static SpriteBatch spriteBatch;
        Texture2D startScreen, endScreen, healthImage, background;
        public static Rectangle viewportRect;
        public static Texture2D enemyBulletSprite, playerMissileSprite, playerBombSprite,
            bombExplosionSprite, enemy1Sprite, enemy2Sprite, powerupSprite;

        //***********************************************************************
        //********************************* FUNCTIONS ***************************
        // *************** CONSTRUCTOR, INITIALIZE, LOAD CONTENT: *****************
        public MainGame()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            TargetElapsedTime = new TimeSpan(0, 0, 0, 0, 100 / 60);
            IsFixedTimeStep = true;
            this.graphics.PreferredBackBufferWidth = 800;
            this.graphics.PreferredBackBufferHeight = 600;
        }

        protected override void Initialize()
        {
            start = new Vector2(120, graphics.GraphicsDevice.Viewport.Height - 180);
            thePlayer = new Player(this, start); // Add player to game
            if (!Components.Contains(thePlayer))
                Components.Add(thePlayer);

            // Set values for keys (subject to change)
            missileButton = Keys.X;
            bombButton = Keys.Z;
            pauseButton = Keys.Space;
            paused = false;
            k_old = Keyboard.GetState();
            // Countdown before first enemy generates
            r = new Random();
            enemyCountdown = (float)(r.Next(100) + 50);
            maxEnemies = 1;
            missileCountdown = 0;
            bombCountdown = 0;
            base.Initialize();
        }

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            startScreen = Content.Load<Texture2D>("startGame");
            endScreen = Content.Load<Texture2D>("endGame");
            
            /* Maximum X borders are slightly wider than screen, so objects don't disappear 
             *  as soon as they touch the edge of the screen.
             */
            borderLeftX = -20; // Subject to change
            borderRightX = graphics.GraphicsDevice.Viewport.Width - borderLeftX;
            borderY = graphics.GraphicsDevice.Viewport.Height;

            // Set positions for the score and health
            scorePos = new Vector2(5, 5);
            healthTextPos = new Vector2(200, 5);
            healthBarPos = healthTextPos + new Vector2(90, 0);
            finalScorePos = new Vector2(420, 320);
            pausePos = new Vector2(300,200);
            healthImage = Content.Load<Texture2D>("lifebar");
            Font1 = Content.Load<SpriteFont>("SpriteFont1");
            background = Content.Load<Texture2D>("background");
           
            // Load sprites that will be used a lot: Enemies, bullets, etc
            enemyBulletSprite = Content.Load<Texture2D>("ref_enemyBullet");
            playerBombSprite = Content.Load<Texture2D>("ref_playerBomb");
            playerMissileSprite = Content.Load<Texture2D>("ref_playerMissile");
            bombExplosionSprite = Content.Load<Texture2D>("ref_bombExplosion");
            enemy1Sprite = Content.Load<Texture2D>("ref_enemyA");
            enemy2Sprite = Content.Load<Texture2D>("ref_armoredCar");
            powerupSprite = Content.Load<Texture2D>("ref_powerUp");

            base.LoadContent();
        }

        // Projectile controls
        public void FireMissile()
        {
            // Creates new missile in front of player
            Vector2 pos = thePlayer.Position + new Vector2(45, 5);
            PlayerMissiles.Add(new PlayerMissile(this, pos));
        }

        public void DropBomb()
        {
            // Creates new bomb below player
            Vector2 pos = thePlayer.Position + new Vector2(0, 30);
            PlayerBombs.Add(new PlayerBomb(this, pos));
        }

        // Reset the game: Called at beginning of new game
        public void ResetGame()
        {
            finalScore = currentScore;
            currentScore = 0;
            thePlayer = new Player(this, start); // Add player to game
            Components.Add(thePlayer);
            thePlayer.Health = 100;
            thePlayer.IsAlive = true;
            currentState = GameState.EndScreen;
        }

        // Reset the game: Called at end of game
        public void NewGame()
        {
            currentState = GameState.StartScreen;
            maxEnemies = 1;
            Enemies.Clear();
            EnemyBullets.Clear();
            PlayerMissiles.Clear();
            PlayerBombs.Clear();
            BombExplosions.Clear();
            Components.Clear();
            Components.Add(thePlayer);
        }

        //**********************************************************************
        //********************* UPDATE METHODS  ********************************

        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // Check: Is the game not running?
            if (currentState == GameState.RunningGame)
            {
                // THINGS TO DO WHILE GAME IS RUNNING
                //if (paused)
                //{
                //    if (pausePressed) paused = false;
                //    return;
                //}

                UpdateInput();
                if (paused) return;
                dt = (float)gameTime.ElapsedGameTime.TotalSeconds;

                // Check: Is player alive?
                if (!thePlayer.IsAlive)
                {
                    if (!Components.Contains(deathExplosion))
                    {
                        deathExplosion = new EffectExplosion(this, thePlayer.Position);
                        Components.Add(deathExplosion);
                    }

                    else if (!deathExplosion.IsAlive)
                    {
                        ResetGame();
                        Components.Remove(deathExplosion);
                        currentState = GameState.EndScreen;
                    }
                }

                playerRectangle = new Rectangle(
                (int)(thePlayer.Position.X - thePlayer.Offset.X),
                (int)(thePlayer.Position.Y - thePlayer.Offset.Y),
                thePlayer.sprite.Width, thePlayer.sprite.Height);

                UpdateBombs();
                UpdateMissiles();
                UpdateEnemyBullets();
                UpdateEnemies();
                UpdatePowerups();
                UpdateNewObjects(dt);
                if (missileCountdown > 0) missileCountdown -= dt;
                if (bombCountdown > 0) bombCountdown -= dt;

            }
            else
            {
                KeyboardState k = Keyboard.GetState();
                if (k.IsKeyDown(Keys.Enter))
                    NewGame();
                UpdateInput();
                UpdateSplashScreen();
            }

            base.Update(gameTime);
        }

        public void UpdateNewObjects(float t)
        {
            // generate powerups
            int newPowerup = r.Next(5000);
            if (newPowerup == 1) // 1/5000 chance
            {
                int xx = r.Next(borderRightX - borderLeftX);
                int yy = r.Next(10) % 2;
                if (yy != 0) yy = borderY; // set at bottom of screen
                else yy = 50; // set at top of screen (not y=0)
                Powerups1.Add(new PowerupHealth(this, new Vector2(xx,yy)));
            }

            passedTime += t;
            if (passedTime > 5 + maxEnemies && maxEnemies<15)
            {
                // allow more enemies over time, to change difficulty
                passedTime = 0;
                maxEnemies++;
            }
            if (Enemies.Count >= maxEnemies) return; // too many enemies on screen
            
            // Creating new enemies!
            if (enemyCountdown > 0) enemyCountdown--;
            else
            {
                int index = r.Next(10);
                if (index <= 6)
                {
                    // New enemy 1 (airship)
                    int y = 70 + r.Next(425);
                    Enemies.Add(new Enemy1(this,
                        new Vector2(borderRightX + 20, y)));
                }
                else
                {
                    // New enemy 2 (tank)
                    Enemies.Add(new Enemy2(this,
                        new Vector2(borderRightX + 20, borderY - 60)));
                }
                enemyCountdown = (float)(3 + r.Next(15));
                // Intervals between enemies vary
            }
        }
        
        public void UpdateBombs()
        {
            
            // Bomb -> explode on impact with ground
            for (int i = 0; i < PlayerBombs.Count; i++)
            {
                PlayerBomb b = PlayerBombs.ElementAt(i);
                if (b.posY > graphics.GraphicsDevice.Viewport.Height - 20)
                    b.Explode();
                for (int j = 0; j < Enemies.Count; j++)
                {
                    Enemy e = Enemies.ElementAt(j);
                    if (b.HitsEnemy(e))
                    {
                        b.Explode();
                    }
                }
            }

            // BombExplosion-> Enemy collision handling
            for (int i = 0; i < BombExplosions.Count; i++)
            {
                BombExplosion be = BombExplosions.ElementAt(i);
                for (int j = 0; j < Enemies.Count; j++)
                {
                    Enemy e = Enemies.ElementAt(j);
                    if (be.CollideWithEnemy(e))
                    {
                        e.Die();
                        Components.Add(new EffectExplosion(this, e.Position));
                        currentScore += e.pointValue * 2 / 3;
                    }
                }
            }
        }

        public void UpdateEnemyBullets()
        {
            // EnemyBullet-> Player collision handling
            for (int i = 0; i < EnemyBullets.Count; i++)
            {
                EnemyBullet eb = EnemyBullets.ElementAt(i);
                // Check if off-screen
                if ((eb.posX < borderLeftX) || (eb.posX > borderRightX)
                    || (eb.posY < -5) || (eb.posY > borderY))
                {
                    eb.Die();
                    continue;
                }

                // Collision with player?
                if (eb.HitsPlayer(playerRectangle))
                {
                    eb.Die();
                    thePlayer.Damage(10);
                }
            }
        }

        public void UpdateEnemies()
        {
            // Enemy-> Player collision handling
            for (int i = 0; i < Enemies.Count; i++)
            {
                Enemy e = Enemies.ElementAt(i);
                // Check if off-screen
                if (e.posX < borderLeftX)
                {
                    e.Die();
                    continue;
                }

                // Collision with player?
                if (e.CollideWithPlayer(playerRectangle))
                {
                    e.Die();
                    Components.Add(new EffectExplosion(this, e.Position));
                    thePlayer.Damage(20);
                    currentScore += e.pointValue / 5;
                }
            }
        }

        public void UpdateInput()
        {
            k_new = Keyboard.GetState();
            if (k_new.IsKeyDown(Keys.Escape) && !k_old.IsKeyDown(Keys.Escape))
            {
                if (currentState != GameState.EndScreen)
                {
                    currentState = GameState.EndScreen;
                    ResetGame();
                }
                else if (currentState != GameState.RunningGame)
                    this.Exit();
            }

            if (paused)
            {
                if (k_new.IsKeyDown(pauseButton) && !k_old.IsKeyDown(pauseButton))
                    paused = false;
            }
            else
            {
                if (k_new.IsKeyDown(pauseButton) && !k_old.IsKeyDown(pauseButton))
                    paused = true;
                else if (missileCountdown <= 0 && k_new.IsKeyDown(missileButton) && !k_old.IsKeyDown(bombButton))
                    {
                        FireMissile();
                        missileCountdown = 0.45f;
                    }
                    
                else if (bombCountdown <= 0 && k_new.IsKeyDown(bombButton) && !k_old.IsKeyDown(missileButton))
                    {
                        DropBomb();
                        bombCountdown = 0.95f;
                    }
            }

            k_old = k_new;

        }

        public void UpdateMissiles()
        {
            // PlayerMissile-> Enemy collision handling
            for (int i = 0; i < PlayerMissiles.Count; i++)
            {
                PlayerMissile m = PlayerMissiles.ElementAt(i);
                // Check if off-screen
                if (m.posX > borderRightX)
                {
                    m.Die();
                    continue;
                }

                for (int j = 0; j < Enemies.Count; j++)
                {
                    Enemy e = Enemies.ElementAt(j);
                    if (m.HitsEnemy(e))
                    {
                        e.Die();
                        m.Die();
                        Components.Add(new EffectExplosion(this, e.Position));
                        currentScore += e.pointValue;
                    }
                }
            }
        }

        public void UpdatePowerups()
        {
            for (int i = 0; i < Powerups1.Count; i++)
            {
                PowerupHealth p = Powerups1.ElementAt(i);
                // Check if off-screen
                if (p.posY < 0 || p.posY > borderY)
                {
                    p.Die();
                    continue;
                }

                // Collision with player?
                if (p.CollideWithPlayer(playerRectangle))
                {
                    p.Die();
                    thePlayer.Damage(-10); // heal until full health
                    if (thePlayer.Health > 100) thePlayer.Health = 100;
                    currentScore += 5;
                }
            }
        }

        public void UpdateSplashScreen()
        {
            KeyboardState keyState = Keyboard.GetState();
            if (keyState.IsKeyDown(Keys.Enter))
            {
                if (currentState != GameState.RunningGame)
                    thePlayer.Position = start;
                currentState = GameState.RunningGame;
                // Old keyboard state does not matter here
            }
        }

        //************************************************************************
        //***********************           DRAW        **************************

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);
            
            // TODO: Add your drawing code here
            spriteBatch.Begin();
            // When the game is not running, draw the start or end screen

            if (currentState != GameState.RunningGame)
            {
                if (currentState == GameState.StartScreen)
                    spriteBatch.Draw(startScreen, Vector2.Zero, null, Color.White);
                else
                    DrawEndScreen();
                spriteBatch.End();
                return;
            }
            spriteBatch.Draw(background, Vector2.Zero, Color.White);
            spriteBatch.Draw(healthImage, healthBarPos,
                new Rectangle(0, 0, 401, 26), Color.Red);
            spriteBatch.Draw(healthImage, healthBarPos,
                new Rectangle(0, 0, thePlayer.Health * 4, 25), Color.White);
            spriteBatch.DrawString(Font1, "Score: " + currentScore.ToString(), scorePos, Color.Yellow);
            spriteBatch.DrawString(Font1, "Health: ", healthTextPos, Color.Yellow);
            if (paused)
                spriteBatch.DrawString(Font1, "PAUSED", pausePos, Color.DarkRed,
                    0, Vector2.Zero, 3, SpriteEffects.None, 0);
            spriteBatch.End();

            // Logic for drawing the Player is in the Player.cs file

            base.Draw(gameTime);
        }

        public void DrawEndScreen()
        {
            spriteBatch.Draw(endScreen, Vector2.Zero, null, Color.White);
            spriteBatch.DrawString(Font1, finalScore.ToString(), finalScorePos, Color.Yellow, 0,
                Vector2.Zero,2,SpriteEffects.None, 0);
        }

        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }
    }
}